import { EventEmitter } from '../utils/EventEmitter';

export interface GameLoopOptions {
    targetFPS?: number;
    maxFPS?: number;
    minFPS?: number;
    fixedTimeStep?: number;
    maxFrameSkip?: number;
}

export class GameLoop extends EventEmitter {
    private targetFPS: number;
    private maxFPS: number;
    private minFPS: number;
    private fixedTimeStep: number;
    private maxFrameSkip: number;
    
    private isRunning: boolean;
    private lastTime: number;
    private accumulator: number;
    private frameCount: number;
    private frameTime: number;
    private fps: number;
    private deltaTime: number;
    
    constructor(options: GameLoopOptions = {}) {
        super();
        
        // Initialize options with defaults
        this.targetFPS = options.targetFPS || 60;
        this.maxFPS = options.maxFPS || 120;
        this.minFPS = options.minFPS || 30;
        this.fixedTimeStep = options.fixedTimeStep || 1/60;
        this.maxFrameSkip = options.maxFrameSkip || 5;
        
        // Initialize state
        this.isRunning = false;
        this.lastTime = 0;
        this.accumulator = 0;
        this.frameCount = 0;
        this.frameTime = 0;
        this.fps = 0;
        this.deltaTime = 0;
        
        // Bind methods
        this.update = this.update.bind(this);
        this.render = this.render.bind(this);
        this.gameLoop = this.gameLoop.bind(this);
    }
    
    /**
     * Start the game loop
     */
    public start(): void {
        if (this.isRunning) return;
        
        this.isRunning = true;
        this.lastTime = performance.now();
        this.accumulator = 0;
        this.frameCount = 0;
        this.frameTime = 0;
        this.fps = 0;
        
        requestAnimationFrame(this.gameLoop);
        this.emit('start');
    }
    
    /**
     * Stop the game loop
     */
    public stop(): void {
        this.isRunning = false;
        this.emit('stop');
    }
    
    /**
     * Set the target FPS
     */
    public setTargetFPS(fps: number): void {
        this.targetFPS = Math.max(this.minFPS, Math.min(this.maxFPS, fps));
        this.emit('targetFPSChanged', this.targetFPS);
    }
    
    /**
     * Get the current FPS
     */
    public getFPS(): number {
        return this.fps;
    }
    
    /**
     * Get the current delta time
     */
    public getDeltaTime(): number {
        return this.deltaTime;
    }
    
    /**
     * Get the fixed time step
     */
    public getFixedTimeStep(): number {
        return this.fixedTimeStep;
    }
    
    /**
     * Set the fixed time step
     */
    public setFixedTimeStep(step: number): void {
        this.fixedTimeStep = step;
        this.emit('fixedTimeStepChanged', step);
    }
    
    /**
     * The main game loop
     */
    private gameLoop(currentTime: number): void {
        if (!this.isRunning) return;
        
        // Calculate frame time and delta time
        const frameTime = currentTime - this.lastTime;
        this.lastTime = currentTime;
        
        // Update FPS calculation
        this.frameCount++;
        this.frameTime += frameTime;
        if (this.frameTime >= 1000) {
            this.fps = Math.round((this.frameCount * 1000) / this.frameTime);
            this.frameCount = 0;
            this.frameTime = 0;
            this.emit('fpsUpdate', this.fps);
        }
        
        // Calculate delta time in seconds
        this.deltaTime = frameTime / 1000;
        
        // Cap delta time to prevent spiral of death
        this.deltaTime = Math.min(this.deltaTime, 0.25);
        
        // Add to accumulator
        this.accumulator += this.deltaTime;
        
        // Fixed time step updates
        let updateCount = 0;
        while (this.accumulator >= this.fixedTimeStep && updateCount < this.maxFrameSkip) {
            this.update(this.fixedTimeStep);
            this.accumulator -= this.fixedTimeStep;
            updateCount++;
        }
        
        // If we're falling behind, emit a warning
        if (updateCount >= this.maxFrameSkip) {
            this.emit('frameSkip', updateCount);
        }
        
        // Render with interpolation
        const alpha = this.accumulator / this.fixedTimeStep;
        this.render(alpha);
        
        // Schedule next frame
        requestAnimationFrame(this.gameLoop);
    }
    
    /**
     * Update game state
     */
    private update(deltaTime: number): void {
        this.emit('update', deltaTime);
    }
    
    /**
     * Render the game
     */
    private render(alpha: number): void {
        this.emit('render', alpha);
    }
    
    /**
     * Reset the game loop state
     */
    public reset(): void {
        this.lastTime = performance.now();
        this.accumulator = 0;
        this.frameCount = 0;
        this.frameTime = 0;
        this.fps = 0;
        this.deltaTime = 0;
        this.emit('reset');
    }
    
    /**
     * Check if the game loop is running
     */
    public isActive(): boolean {
        return this.isRunning;
    }
    
    /**
     * Pause the game loop
     */
    public pause(): void {
        if (this.isRunning) {
            this.isRunning = false;
            this.emit('pause');
        }
    }
    
    /**
     * Resume the game loop
     */
    public resume(): void {
        if (!this.isRunning) {
            this.isRunning = true;
            this.lastTime = performance.now();
            requestAnimationFrame(this.gameLoop);
            this.emit('resume');
        }
    }
} 